/**
	License:		Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:		OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:			EngObject.d

	Description:
		basic engine object class. We suppose it to be a scene object, so:
		positioned & oriented, optionally animated (movable at least), part of objects tree

	Date:			11.04.2008 by Digited
*/
module moonglide.core.engobjects.EngObject;

public import
	moonglide.io.LogSystem,
	moonglide.core.Events,
	moonglide.math.MoonMath,
	moonglide.util.MoonUtil,
	moonglide.resources.Management;

/** Animator is a "strategy" for scene objects that receives input between scene runs
(by scene events, generated in scene's onEventCallback, or from GUI controls, for example,
because animator is an event listener and can listen to ANY generator, including other animators)
and stores it as internal data, some sort of "ToDo-data": when scene object is updated,
it iterates all animators (calling animate( frametime )), so they work with object's members
using last received data and their own logic and mechanics.

Example: engine camera class. As a basic engine object, it has only position and orientation,
but it can hold animators, that will bring a shooter camera behavior (no roll,
pitch only +- 90 deg, euler angles),  newtonian free camera behavior (no up or down,
no gimble locks, moving and rotating using own orientation vectors),
fixed point camera behavior, arcball object-centered behavior or anything else.
Speaking about newtonian free camera behavior animator, "ToDO-data" are linear and angular
acceleration data members, transformed (while animate()) to speed and finally to new position
and orientation.

Animator <-> object relationship can be 1 per 1, many per 1, 1 per many.

Animators can also be modifiers to other animators, thus they can be used for gravity and wind effects
for particle systems, or maybe anything you can imagine and implement.

NOTICE: usually, perfomance is the cost of flexiblity. Animators are not the panacea and must be used
where there is a real need to use them. Objects with specialized behavior and less indirect
interface to special members are always computed faster. Also, animator listen subscribes must
be used with caution.*/

/// interface for all animators of scene objects or groups of them
interface IAnimator : IEventListener, IEventGenerator
{
	void animate( float time );
}

//*************************************************************************************************************************

/**
 * base abstract class for all engine objects
 */
 abstract class CEngObject : IEventListener, IEventGenerator
{
	mixin TEventGenerator;

	private:
		static CEngObject[char[]] _allEngObjects;

        /**
         * register id of CEngObject in static global register array
         */
		static void _register( CEngObject obj )
		{
			debug if( obj.id in _allEngObjects )
				moonAssert( false, obj.id~": CEngObject._register: trying to register already registered object", __FILE__, __LINE__ );

			_allEngObjects[ obj.id ] = obj;
		}

        /**
         * unregister id of CEngObject in static global register array
         */
		static void _unregister( CEngObject obj )
		{
			if( obj.id in _allEngObjects )
				_allEngObjects.remove( obj.id );
			else
				moonAssert( false, obj.id~": CEngObject._unregister: trying to unregister unregistered object", __FILE__, __LINE__ );
		}

	protected:
		bool		_visible;
		Vector3f	_size;

	public:
		/**
         * get list id of CEngObject in static global register array
         * @return list of id
         */
		static char[][] listObj()
		{
		    char[][] res;
			foreach( obj; _allEngObjects )
                res ~= obj.id;
            return res;
		}

		int 		rendPriority = 10;		/// sort priority. if( a.rendPriority > b.rendPriority ) -> b.render and only then a.render
		float		sortIndx = 0.0f;

		/**
         * opCmp is used for sort arrays by "sortIndx"
         */
        final int opCmp( Object o )
		{
			float sIo = (cast(CEngObject)o).sortIndx;
			if( this.sortIndx < sIo )
				return -1;

			if( this.sortIndx > sIo )
				return 1;

			return 0;
		}

		/**
         * get CEngObject from static global register array
         */
        static CEngObject getObject( char[] id )
		{
			if( id in _allEngObjects )
				return _allEngObjects[ id ];
			else
			{
				traceLog.warn( "CEngObject.GetObject: " ~ id ~ " not found, returning null" );
				return null;
			}
		}

//--------------------------------------------------------------------------------------------------
		Vector3f	position,
					wposition;

		Matrix33f	orientation,
					worientation;

		void		visible( bool vis )	{ _visible = vis; }
		bool		visible()			{ return _visible; }

		Vector3f	size()				{ return _size; }
		void		size( Vector3f sz )	{ _size = sz; }

		bool		isPointAbove( Size2ui point ) { return false; }
//--------------------------------------------------------------------------------------------------

		CEngObject				parent;		/// parent object (contains this object in children)
		CEngObject[ char[] ]	children;	/// hash map with children, key is idName string
		IAnimator[ char[] ]		animators;

		bool
			isStatic,								/// not movable
			needUpdate	= true;						/// if must do update( time )

		float disToCam;								/// Distance to active camera


		/**
         * Use all animators of current node
         */
        void updateAnimators( float timeelapsed )
		{
			foreach( inout anim; animators )
				if( anim )
					anim.animate( timeelapsed );
		}

		/**
         * update all children of current node.
         * When override this, use super.update at the end (after changing self' position)
         */
		void update( float timeelapsed, ref Vector3f parentWPos, ref Matrix33f parentWOrient )
		{
			if( !needUpdate )
				return;

//			updateAnimators( timeelapsed );   I moved it down. Where is right position?

			worientation = orientation * parentWOrient;
			wposition = parentWOrient.inverse * position + parentWPos;


			updateAnimators( timeelapsed ); // here par systems attached correctly

			visible(( disToCam > floats["begVis"] ) && ( disToCam < floats["endVis"] ));

			foreach( inout child; children )
				child.update( timeelapsed, wposition, worientation );
		}


		/**
         * OnRender event
         */
		void onRender()
		{
			// Do something just before rendering. Called from SceneManager on scenegraph pass
		}


		/**
         * Delete all children of current node
         */
		void delAllChidren( bool destroy = true )
		{
			if( destroy )
				foreach( inout child; children )
					delete child;

			children = null;
		}

        /**
         * Delete child, if destroy then delete object too
         */
		void delChild( CEngObject pNode, bool destroy = true )
		in
		{
			moonAssert( pNode !is null, "deleting child is null", __FILE__, __LINE__ );
		}
		body
		{
			CEngObject* find = pNode.id in children;

			if( find !is null )
			{
				children.remove( pNode.id );

				if( destroy )
					delete pNode;
			}
		}

        /**
         * Add child
         */
		void addChild( CEngObject pNode )
		in
		{
			assert(pNode !is null);
			assert(pNode !is this);
		}
		body
		{
			debug debugLog.trace( id ~ ": Add child( " ~ pNode.id ~ " )... " );

            if( pNode == parent )
            {
                // swap nodes
                CEngObject parparent = parent.parent;
                parparent.delChild( parent, false );
                parparent.addChild( this );
            }

			CEngObject* find = pNode.id in children;

			// new child?
			if( find is null)
			{
                debug debugLog.trace( id ~ ": Add child( " ~ pNode.id ~ " )... this is a new child, added " );
				// if pNode (new child) have parent - delete it from old parent
				if( pNode.parent !is null )
					pNode.parent.delChild( pNode, false );


				pNode.parent = this;
				children[ pNode.id ] = pNode;
			}
			else
                moonAssert( false, id~": child is already added", __FILE__, __LINE__ );
//				debug debugLog.trace( "Child is already Added.. Error?" );
		}

		//================================================================
		/// default event stuff
		//================================================================
		void onEventCallback( CEvent event, IEventGenerator sender ) {}
		void onEventReceive( CEvent event, IEventSender sender ) {}

		/**
         * constructor needs string with id
         */
        this( char[] idname )
		{
		    bool test( char[] n )
		    {
		        return (n in _allEngObjects) is null;
		    }

			// check for unique object id
			//_id = _FixID( idname );	// static
			_id = uniqName (idname, &test);
			_register( this );			// static

			visible = needUpdate = true;

			floats["begVis"]  = -float.max;
			floats["endVis"]  = float.max;

			position = wposition = _size = Vector3f( 0.f, 0.f, 0.f );
			orientation = worientation = Matrix33f.identity;
			orientation[2].set(0,0,-1); // right-sided orientation system: x-right, y-up, looking against z
			worientation[2].set(0,0,-1);
		}

		/**
         * destructor stuff
         */
        ~this()
		{
			foreach( inout anim; animators )
			{
				if( anim )
					delete anim;
			}

			animators = null;

			debug debugLog.trace( "engine object destroyed: " ~ _id );

			if( children && children.length > 0 )
				delAllChidren();

			_unregister( this );		// static
		}
}
